/******************************************************************************************************
Copyright (C) 2021 Mestrelab Research S.L. All rights reserved.

This file is part of the MNova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*****************************************************************************************************/

function AnalyseMixtures() {
	"use strict";

    var dialog, showDialog, runCmd, process;

	//Registry entries
	var inputFileKey = "Input Spectrum";
	var outputFileKey = "Output Spectrum";
	var inputReferenceKey = "Reference Library Of Pure Compounds";
	var jarKey = "Copy of ml-mixture.jar";

	//Load GUI
	dialog = Application.loadUiFile('ricares:AnalyseMixtures.ui');
	showDialog = true;

	//Load settings to the GUI
	data = settings.value(inputFileKey, "");
	if (data !== "")
		dialog.widgets.leInputFile.text = data;

	data = settings.value(outputFileKey, "");
	if (data !== "")
		dialog.widgets.leOutputFile.text = data;

	data = settings.value(inputReferenceKey, "");
	if (data !== "")
		dialog.widgets.leInputReference.text = data;

	data = settings.value(jarKey, "");
	if (data !== "")
		dialog.widgets.leJar.text = data;

	dialog.widgets.tbInputFile.clicked.connect(dialog.widgets.leInputFile, CommonSlots.onOpenFile);
	dialog.widgets.tbOutputFile.clicked.connect(dialog.widgets.leOutputFile, CommonSlots.onOpenFile);
	dialog.widgets.tbReferenceFile.clicked.connect(dialog.widgets.leReferenceFile, CommonSlots.onOpenFile);
	dialog.widgets.tbJar.clicked.connect(dialog.widgets.leJar, CommonSlots.onOpenFile);

	//Launch GUI and get the user response
    var inputFile, outputFile, referenceFile, jarFile;
	while (showDialog) {

		if (!dialog.exec())
			return;

		inputFile = new File(dialog.widgets.leInputFile.text);
		if(!inputFile.exists){
			MessageBox.critical('The input file does not exist');
			continue;
		}

		outputFile = new File(dialog.widgets.leOutputFile.text);

		referenceFile = new File(dialog.widgets.leReferenceFile.text);
		if(!referenceFile.exists){
			MessageBox.critical('The reference file does not exist');
			continue;
		}

        jarFile = new File(dialog.widgets.leJar.text);
		if(!jarFile.exists){
			MessageBox.critical('ml-mixture.jar does not exist');
			continue;
		}

		showDialog = false;
	}

	progress = new ProgressDialog();
	progress.labelText = "Running mixture analysis. Please wait...";
	progress.minimum = 0;
	progress.maximum = 100;
	progress.value = 0;

	var process = new Process();
	process.start("java.exe", ["-Xmx1g", "-cp", jarFile.name, "bruker.bio.apps.ml.mixture.api.MLMixture", inputFile.name, outputFile.name, referenceFile.name]);
    progress.value = 1;

    var timeout = 2000000;
    var log = "";
	if (!process.waitForStarted()) {
		log += qsTr("Error launching the Java virtual machine");
	} else {
		var counter = 1000;
		process.waitForFinished(1000);
		progress.value = 2;
		while (counter < timeout) {
            if (counter % (timeout / 40) == 0)
				++progress.value;
			if (process.state === Process.NotRunning) {
				if (!process.exitStatus === Process.NormalExit) {
					log += qsTr("Error: Process did not exit normally");
				}
				break;
			} else {
				process.waitForFinished(1000);
			}
			counter = counter + 1000;
		}

		//Timeout issue
		if (counter >= timeout) {
			log += qsTr("Error: Exceeded time of {0} seconds".format(timeout));
			process.kill();
		}
	}


	//process = new Process();
	//process.start("java.exe", ["-Xmx1g", "-cp", jarFile.name, "bruker.bio.apps.ml.mixture.api.MLMixture", inputFile.name, outputFile.name, referenceFile.name]);
	//process.waitForFinished(2000000);

    progress.value = 43;
	progress.labelText = "Processing input spectrum";

	// Parse the result file
    var outputJson = AnalyseMixtures.parseJSON(outputFile);
	if (outputJson.status != "ok")
		MessageBox.critical("Problem in output, status does not equal OK");

    var spectrumList = [];

    var inputJson = AnalyseMixtures.parseJSON(inputFile);
	var inputAmplitudes = inputJson.amplitudes;
	var baseFrequency = inputJson.base_frequency;

	var inputSpectrum = AnalyseMixtures.createSpectrum("1H", baseFrequency, inputJson.right_ppm, inputJson.left_ppm, inputFile.name, "Input Spectrum", inputAmplitudes);
    spectrumList.push(inputSpectrum);

	progress.labelText = "Calculating output spectrum";
    progress.value = 60;

    // Calculate output amplitudes (including line broadening)
	var outputAmplitudes = AnalyseMixtures.calculateOutputAmplitudes(inputAmplitudes.length, baseFrequency, inputJson, outputJson);

	var outputSpectrum = AnalyseMixtures.createSpectrum("1H", baseFrequency, inputJson.right_ppm, inputJson.left_ppm, outputFile.name, "Output Spectrum", outputAmplitudes);
    spectrumList.push(outputSpectrum);

	progress.labelText = "Displaying results";
    progress.value = 80;

    var stack = nmr.createArrayedItem(spectrumList, false);

	var items = AnalyseMixtures.normaliseMixtureCoeffcients(outputJson);
	var tableItems = items[0];
	var fileItems = items[1];
	dialog = Application.loadUiFile("ricares:AnalyseMixturesResults.ui");
    dialog.widgets.pushButton.clicked.connect(function () {
        var exportName = FileDialog.getSaveFileName("*.csv", "Choose file", Dir.home(), 4);
		exportFile = new File(exportName);
		var success = exportFile.open(File.WriteOnly);
		if (!success) {
			MessageBox.critical("File cannot be created");
			return;
		}

		exportStream = new TextStream(exportFile);
		for (var i = 0; i < fileItems.length; ++i)
		    exportStream.writeln(fileItems[i][0] + ", " + fileItems[i][1]);

		exportFile.close();
    	}
    );

    progress.value = 100;

	dialog.widgets.tableWidget.horzHeaderItems = ["Molecule", "Composition"];
	dialog.widgets.tableWidget.items = tableItems;
	dialog.widgets.tableWidget.sortByColumnWithOrder(1, 1);
	dialog.widgets.tableWidget.setResizeMode("Stretch");
	dialog.exec();


	inputFile.close();
	referenceFile.close();
	outputFile.close();
	jarFile.close();
}

AnalyseMixtures.parseJSON = function (aFile) {
	"use strict";

	aFile.open(File.ReadOnly);
	var stream = new TextStream(aFile);
	var json = JSON.parse(stream.readAll());
	return json;
}

AnalyseMixtures.normaliseMixtureCoeffcients = function (aOutputJson) {
	"use strict";

	var nMolecules = aOutputJson.result.length;

	// Normalise mixture coefficients
	var squareCoefficents = 0;
	var coefficent;
	for (var i = 0; i < nMolecules; ++i) {
		coefficent = parseFloat(aOutputJson.result[i].scaling);
		squareCoefficents += coefficent * coefficent;
	}

	var length = Math.sqrt(squareCoefficents);
	var lengthCheck = 0;
	var tableItems = [];
	var fileItems = [];
    var normalisedCoefficent, percentage;

	for (i = 0; i < nMolecules; ++i) {
		coefficent = parseFloat(aOutputJson.result[i].scaling);
		normalisedCoefficent = coefficent / length;
		percentage = (normalisedCoefficent * normalisedCoefficent) * 100;
		lengthCheck += percentage;
        if (percentage > 0.00000009) {
            tableItems.push([aOutputJson.result[i].name, percentage.toFixed(2)]);
            fileItems.push([aOutputJson.result[i].name, percentage]);
        }
	}

	if (Math.abs(lengthCheck - 100.0) > 1e-8)
		MessageBox.critical("Problem with normalisation: Total composition percentage = " + lengthCheck);

    return [tableItems, fileItems];
}

AnalyseMixtures.createSpectrum = function (aNucleus, aFrequency, aFromPpm, aToPpm, aFileName, aTitle, aAmplitudes) {
	"use strict";

	var spectrum = new NMRSpectrum({
		1: {
			nucleus: aNucleus,
			frequency: aFrequency,
			from_ppm: aFromPpm,
			to_ppm: aToPpm,
			size: aAmplitudes.length
		},
		filename: aFileName,
		origin: "",
		title: aTitle
	});

	nmr.beginModification(spectrum);
	spectrum.setReal("all", aAmplitudes);
	nmr.endModification(spectrum);

    return spectrum
}

AnalyseMixtures.calculateOutputAmplitudes = function (aSize, aBaseFrequency, aInputJson, aOutputJson) {
	"use strict";

	var outputAmplitudes = new Array(aSize);
	var lineLorentzian = new Array(aSize);
	var lineGaussian = new Array(aSize);
	for (var i = 0; i < outputAmplitudes.length; ++i) {
		outputAmplitudes[i] = 0.0;
	}

	// Evenly arrange numbers between left_ppm and right_ppm - this is like np.linspace()
	var scale = new Array();
	for (var i = 0; i < aSize; ++i) {
		scale[i] = aInputJson.left_ppm + i * (aInputJson.right_ppm - aInputJson.left_ppm) / (aSize - 1);
	}

    // Calculate line broadening
	var sqrtLogTwo = 0.832554611157698;
	for (var i = 0; i < aOutputJson.result.length; ++i) {

		if (aOutputJson.result[i].scaling == 0)
        	continue;

		for (var j = 0; j < aOutputJson.result[i].regions.length; ++j) {
			if (String(aOutputJson.result[i].regions[j].assigned) === 'true') {

				var lineWidthPpm = (aOutputJson.result[i].regions[j].width / aBaseFrequency) / 2.0;
				var tmpValue3 = 1.0 - aOutputJson.result[i].regions[j].lineShapeParameter;

				for (var k = 0; k < aOutputJson.result[i].regions[j].ppm.length; ++k) {

					var tmpValue1 = (aOutputJson.result[i].scaling * aOutputJson.result[i].regions[j].intensity[k] / aOutputJson.result[i].regions[j].width)
					var tmpValue2 = aOutputJson.result[i].regions[j].ppm[k] + aOutputJson.result[i].regions[j].shift;

					for (var l = 0; l < outputAmplitudes.length; ++l) {
						var lorentzianFactor = (scale[l] - tmpValue2) / lineWidthPpm;
						lorentzianFactor = 1.0 + (lorentzianFactor * lorentzianFactor);
						lineLorentzian[l] = tmpValue1 / lorentzianFactor;

						var gaussianFactor = (scale[l] - tmpValue2) / lineWidthPpm * sqrtLogTwo;
						gaussianFactor = Math.exp(-(gaussianFactor * gaussianFactor));
						lineGaussian[l] = tmpValue1 * gaussianFactor;

						outputAmplitudes[l] += tmpValue3 * lineLorentzian[l] + aOutputJson.result[i].regions[j].lineShapeParameter * lineGaussian[l];
					}
				}
			}
		}
	}

    return outputAmplitudes
}

if (this.MnUi && MnUi.scripts_nmr) {
    MnUi.scripts_nmr.scripts_nmr_AnalyseMixtures = AnalyseMixtures;
}
